using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace TAAlert.CommonUtils
{
    public enum Signal { Buy = 1, Neutral = 0, Sell = -1 };

    class EMAEvaluator
    {
        #region EMA Calculations
        /// <summary>Recursive formula for single step in EMA calculation</summary>
        /// <param name="ema0">value of exponentially weighted moving average at time t (yesterday)</param>
        /// <param name="nEMA">Period for exponential weighting. 2/(1+n) = 1-lambda in RiskMetrics notation</param>
        /// <param name="value1">Value to be averaged at time t+1 (today)</param>
        /// <returns>ema1 = exponentially weighted moving average at time t+1 (today)</returns>
        public static double calcEMA(double ema0, int nEMA, double value1)
        {   // ema(t+1) = ema(t) + 2/(1+nEMA) * (price(t+1) - ema(t))
            return ema0 + 2 * (value1 - ema0) / (1 + nEMA);
        }

        public static List<double> calcEMAArray(IList<double> value, int nEMA)
        {
            int n = value.Count;
            List<double> ema = new List<double>(n);
           
            if (n > 0)
                ema.Add(value[0]);  // ema[0] = value[0]
            for (int i = 1; i < n; ++i)
                ema.Add(calcEMA(ema[i - 1], nEMA, value[i]));  //ema[i] = calcEMA(ema[i - 1], nEMA, value[i]);
            
            Debug.Assert(ema.Count == value.Count, "EMA and Value lists must have the same length");
            return ema;
        }

        /// <summary>BUY if shortEMA is above longEMA and SELL if shortEMA is below longEMA</summary>
        /// <param name="emaShort">value of EMA short</param>
        /// <param name="emaLong">value of EMA long</param>
        /// <returns>BUY is short EMA is above (or equal to) long EMA, SELL otherwise</returns>
        public static Signal emaSignal(double emaShort, double emaLong)
        {
            Signal result;
            if (emaShort >= emaLong)
                result = Signal.Buy;
            else if (emaShort < emaLong)
                result = Signal.Sell;
            else // never happens
                result = Signal.Neutral;
            return result;
        }

        /// <summary>EMA signal with high volatility cut-out. If vol below volT then use EMA. Neutral above volT</summary>
        /// <param name="emaShort">value of EMA short</param>
        /// <param name="emaLong">value of EMA long</param>
        /// <param name="vol">value of volatility</param>
        /// <param name="volTreashod">volatility treashold</param>
        /// <returns>
        ///     BUY if emaShort above emaLong (and vol below volT)
        ///     SELL if emaShort below emaLong (and vol below volT)
        ///     NEUTRAL if vol is above volT
        /// </returns>
        public static Signal emaVolCutSignal(double emaShort, double emaLong, double vol, double volTreashod)
        {
            return vemaSignal(emaShort, vol, 1, emaLong, 0, 0, volTreashod);
        }
        #endregion

        #region Volatility Based EMA
        static private readonly int N_VOLATILITY_PERIOD = 60;
        static private readonly int N_OF_DAYS_IN_YEAR = 252;

        /// <summary>Calculate Volatilty based on 60-days period and 252 days in year</summary>
        public static double calcEMAVolatility(double vol0, double price0, double price1)
        {
            return calcEMAVolatility(vol0, price0, price1, N_VOLATILITY_PERIOD, N_OF_DAYS_IN_YEAR);
        }
        public static double calcEMAVolatility(double vol0, double price0, double price1, int nEMA, int nOfPeriodsInYear)
        {
            double r = price1 / price0 - 1;
            return Math.Sqrt(vol0 * vol0 + 2.0 * (nOfPeriodsInYear * r * r - vol0 * vol0) / (1 + nEMA));
        }

        /// <summary>Calculate 60-days EMA volatility</summary>
        /// <param name="price">List of prices</param>
        /// <returns>List with 60 day's volatility</returns>
        public static List<double> calcEMAVolatilityArray(IList<double> price)
        {
            return calcEMAVolatilityArray(price, N_VOLATILITY_PERIOD, N_OF_DAYS_IN_YEAR);
        }

        public static List<double> calcEMAVolatilityArray(IList<double> price, int nEMA, int nOfPeriodsInYear)
        {
            int n = price.Count;
            List<double> volatility = new List<double>(n);
            if (n < 2)
                throw new System.Data.DataException("At least 2 data points required for volatility calculations");
            double vol0 = Math.Abs(price[1] / price[0] - 1) * Math.Sqrt(nOfPeriodsInYear); // forward looking 1 day. Not nice but minor issue
            volatility.Add(vol0);
            for (int i = 1; i < n; ++i)
            {
                double volI = calcEMAVolatility(volatility[i - 1], price[i - 1], price[i], nEMA, nOfPeriodsInYear);
                volatility.Add(volI);
            }

            return volatility;
        }

        /// <summary>Use n1 EMA for vol below volT. Use n2 EMA for vol above volT</summary>
        /// <param name="quote">price</param>
        /// <param name="vol">volatility</param>
        /// <param name="n1LowVol">period for vol below volT</param>
        /// <param name="ema1LowVol">EMA corresponding to n1</param>
        /// <param name="n2HigVol">period for vol above volT</param>
        /// <param name="ema2HighVol">EMA corresponding to n2</param>
        /// <param name="volTreashold">Volatility treashold</param>
        /// <returns>BUY/SELL signal</returns>
        public static Signal vemaSignal(double quote, double vol, int n1LowVol, double ema1LowVol, int n2HigVol, double ema2HighVol, double volTreashold)
        {
            Signal signal;
            if (vol < volTreashold && n1LowVol > 0)
                signal = EMAEvaluator.emaSignal(quote, ema1LowVol);
            else if (vol >= volTreashold && n2HigVol > 0)
                signal = EMAEvaluator.emaSignal(quote, ema2HighVol);
            else
                signal = Signal.Neutral; // if some nEMA <= 0
            return signal;
        }
        #endregion
    }
}
